#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>

#include "ac.h"
#include "acdaemon.h"


typedef struct _async_core {
	int fd_max;
	int isalive;

	ac_callback cb_readfds[255];
	ac_callback cb_writefds[255];
	ac_callback cb_exceptfds[255];

	/*FIXME: First of all using arrays for this is just 
	 * a dirty version so we can quickly get the concept
	 * up and running. I should be using a hashtable of some
	 * sort.
	 *
	 * Secondly, When adding the data elements to the array/hashtable,
	 * and i have complex data objects, I should find a way to properly
	 * malloc these data objects. and then sanely free them. [I just realized
	 * that b/cos these are just POINTERS, there no need to worry about malloc/free
	 * I will CREATE the data types properly outside here, and just keep them here
	 * hence will also destroy them properly outside here.
	 *
	 * Alternatively just use GC_MALLOC and be done with it!!! peh!!!
	 * */
	void* readdata[255];
	void* writedata[255];
	void* exceptdata[255];

	fd_set readfds;
	fd_set writefds;
	fd_set exceptfds;

} *_Async_Core;

static _Async_Core ac = NULL;

int ac_init()
{
	ac = malloc(sizeof(*ac));
	if(!ac) {
		return 0;
	}

	int i = 0;
	for(i = 0; i <= 255; i++) {
		ac->cb_readfds[i] = NULL;
		ac->readdata[i] = NULL;
	}

	for(i = 0; i <= 255; i++) {
		ac->cb_writefds[i] = NULL;
		ac->writedata[i] = NULL;
	}

	for(i = 0; i <= 255; i++) {
		ac->cb_exceptfds[i] = NULL;
		ac->exceptdata[i] = NULL;
	}

	ac->fd_max = 0;
	ac->isalive = 1;


	FD_ZERO(&(ac->readfds));
	FD_ZERO(&(ac->writefds));
	FD_ZERO(&(ac->exceptfds));

	return 1;
}

void ac_shutdown()
{
	int i = 0;
	for(i = 0; i <= 255; i++) {
		if(ac->cb_readfds[i]) {
			ac_callback_unregister(AC_CALLBACK_TYPE_READ, i);
		}

		if(ac->cb_writefds[i]) {
			ac_callback_unregister(AC_CALLBACK_TYPE_WRITE, i);
		}

		if(ac->cb_exceptfds[i]) {
			ac_callback_unregister(AC_CALLBACK_TYPE_EXCEPT, i);
		}
	}

	FD_ZERO(&(ac->readfds));
	FD_ZERO(&(ac->writefds));
	FD_ZERO(&(ac->exceptfds));

	ac->isalive = 0;

	free(ac);
}


int ac_callback_register(int type, int fd, ac_callback cb, void* data, int size)
{
	assert(
			type == AC_CALLBACK_TYPE_READ ||
			type == AC_CALLBACK_TYPE_WRITE ||
			type == AC_CALLBACK_TYPE_EXCEPT
		  );

	if(type == AC_CALLBACK_TYPE_READ) {

		FD_SET(fd, &(ac->readfds));
		ac->cb_readfds[fd] = cb;
		if(data) {
			ac->readdata[fd] = malloc(size);
			memcpy(ac->readdata[fd], data, size);
		}

	} else if(type == AC_CALLBACK_TYPE_WRITE) {

		FD_SET(fd, &(ac->writefds));
		ac->cb_writefds[fd] = cb;
		if(data) {
			ac->writedata[fd] = malloc(size);
			memcpy(ac->writedata[fd], data, size);
		}

	} else if(type == AC_CALLBACK_TYPE_EXCEPT) {

		FD_SET(fd, &(ac->exceptfds));
		ac->cb_exceptfds[fd] = cb;
		if(data) {
			ac->exceptdata[fd] = malloc(size);
			memcpy(ac->exceptdata[fd], data, size);
		}

	}

	ac->fd_max = ac->fd_max > fd ? ac->fd_max : fd + 1;
	return 1;
}

int ac_callback_unregister(int type, int fd)
{
	assert(fd < ac->fd_max);

	if(type == AC_CALLBACK_TYPE_READ) {

		FD_CLR(fd, &(ac->readfds));
		ac->cb_readfds[fd] = NULL;

		if(ac->readdata[fd]) {
			free(ac->readdata[fd]);
		}
		ac->readdata[fd] = NULL;

	} else if(type == AC_CALLBACK_TYPE_WRITE) {

		FD_CLR(fd, &(ac->writefds));
		ac->cb_writefds[fd] = NULL;

		if(ac->writedata[fd]) {
			free(ac->writedata[fd]);
		}
		ac->writedata[fd] = NULL;

	} else if(type == AC_CALLBACK_TYPE_EXCEPT) {

		FD_CLR(fd, &(ac->exceptfds));
		ac->cb_exceptfds[fd] = NULL;

		if(ac->exceptdata[fd]) {
			free(ac->exceptdata[fd]);
		}
		ac->exceptdata[fd] = NULL;

	}

	ac->fd_max = fd == ac->fd_max - 1 ? ac->fd_max - 1 : ac->fd_max;
	return 1;
}

void ac_mainloop_daemonize()
{
	acdaemon_daemonize_default();
	ac_mainloop();
}

void ac_mainloop()
{
	fd_set readfds;
	fd_set writefds;
	fd_set exceptfds;

	while(ac->isalive) {

		FD_ZERO(&readfds);
		FD_ZERO(&writefds);
		FD_ZERO(&exceptfds);

		readfds = ac->readfds;
		writefds = ac->writefds;
		exceptfds = ac->exceptfds;

		if(select(ac->fd_max, &readfds, &writefds, &exceptfds, NULL) == -1) {
			/*TODO: once we unregister, this wil throw an exception before the fdsets are 
			 * updated.. deal with this cleanly*/
			perror("ac - Select");
			return;
		}

		int i;

		for(i = 0; i < ac->fd_max; i++) {

			if(FD_ISSET(i, &readfds)) {

				ac->cb_readfds[i](i, ac->readdata[i]);

			} else if(FD_ISSET(i, &writefds)) {

				ac->cb_writefds[i](i, ac->writedata[i]);

			} else if(FD_ISSET(i, &exceptfds)) {

				ac->cb_exceptfds[i](i, ac->exceptdata[i]);

			} else {

			}

		}

	}
}

